package com.caits.lbs.framework.interceptor;

import java.io.BufferedReader;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import com.caits.lbs.framework.Constants;
import com.caits.lbs.framework.Constants.Common;
import com.caits.lbs.framework.bean.common.ApiParam;
import com.caits.lbs.framework.bean.common.ApiParamRawType;
import com.caits.lbs.framework.bean.common.ApiResult;
import com.caits.lbs.framework.bean.common.ApiResultHead;
import com.caits.lbs.framework.bean.common.CacheKey;
import com.caits.lbs.framework.controller.BaseController;
import com.caits.lbs.framework.services.redis.RedisDAO;
import com.caits.lbs.framework.utils.HttpUtils;
import com.caits.lbs.framework.utils.JsonUtilAlibaba;
import com.caits.lbs.framework.utils.ServletUtils;

/**
 * <p>ApiParamInterceptor</p>
 * <p>用于处理公共ApiParam参数的SpringMVC拦截器</p>
 *
 * @author		周华彬(zhouhuabin@sinoiov.com, zhou_hua_bin@163.com)
 * @version		0.0.0
 * <table style="border:1px solid gray;">
 * <tr>
 * <th width="100px">版本号</th><th width="100px">动作</th><th width="100px">修改人</th><th width="100px">修改时间</th>
 * </tr>
 * <!-- 以 Table 方式书写修改历史 -->
 * <tr>
 * <td>0.0.0</td><td>创建类</td><td>admin</td><td>2017年7月4日 下午3:59:15</td>
 * </tr>
 * <tr>
 * <td>XXX</td><td>XXX</td><td>XXX</td><td>XXX</td>
 * </tr>
 * </table>
*/
public class ApiParamInterceptor implements HandlerInterceptor {
	static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ApiParamInterceptor.class);
//	static final Log LOG = LogFactory.getLog(ApiParamInterceptor.class);
	static final Logger errorLog = org.apache.log4j.Logger.getLogger("errorLog");

	/** 变量:api验证key-map,先定义两个key类型:String */
	public static Map<String,String> apiAuthKeyMap=new HashMap<String,String>();
	{
		apiAuthKeyMap.put("1.0","zjxlhyl2017");
		apiAuthKeyMap.put("2.0","zjxlhyl2019");
	}
	/**
	 * 忽略登录验证的请求列表
	 */
	private final static List<String> IGNORE_TOKEN_URL = new LinkedList<String>();
	{
		/** 变量:登录，获取验证码等请求,类型:long */
		IGNORE_TOKEN_URL.add("login.do");
		IGNORE_TOKEN_URL.add("validateLogin.do");
		IGNORE_TOKEN_URL.add("sendValidateCode.do");
		IGNORE_TOKEN_URL.add("logout.do");
	}
	private final static List<String> IGNORE_CHECK_URL = new LinkedList<String>();
	{
		/** 变量:不一样的请求,类型:long */
		IGNORE_CHECK_URL.add("uploadImg.do");
		IGNORE_CHECK_URL.add("payCallback.do");
	}
	private static final int queueSize = (1000);
	private static final LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<String>(queueSize);
	private static final ThreadLocal<ApiParam<?>> ApiParamCache = new ThreadLocal<ApiParam<?>>();
	
	/** 变量:是否进行签名验证,类型:boolean */
	private static boolean validateSignAndToken = true;
	/** Redis实例 */
	private static RedisDAO redisCacheHelper;

	/** 变量:session缓存时长，秒,类型:long */
	private static long cacheTime = 24*3600;
	static {
		Runnable runnable = new Runnable() {
			public void run() {
				LOG.info("当前任务队列的大小,size:" + queue.size());
			}
		};
		ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
		// 第二个参数为首次执行的延时时间，第三个参数为定时执行的间隔时间
		service.scheduleAtFixedRate(runnable, 1, 1, TimeUnit.HOURS);
	}

	/**
	 * 设置属性cacheTime的值，参数类型long
	 * @param cacheTime the cacheTime to set
	 */
	public void setCacheTime(long cacheTime) {
		this.cacheTime = cacheTime;
	}

	
	/**
	 * 获取属性 cacheTime的值
	 * @return the cacheTime : long
	 */
	public static long getCacheTime() {
		return cacheTime;
	}


	/**
	 * 设置属性apiAuthKeyMap的值，参数类型Map<String,String>
	 * @param apiAuthKeyMap the apiAuthKeyMap to set
	 */
	public static void setApiAuthKeyMap(Map<String, String> apiAuthKeyMap) {
		ApiParamInterceptor.apiAuthKeyMap = apiAuthKeyMap;
	}


	/**
	 * 设置属性redisCacheHelper的值，参数类型RedisCacheHelper
	 * @param redisCacheHelper the redisCacheHelper to set
	 */
	public static void setRedisCacheHelper(RedisDAO redisCacheHelper) {
		ApiParamInterceptor.redisCacheHelper = redisCacheHelper;
	}


	public static void setValidateSignAndToken(boolean validateSignAndToken) {
		ApiParamInterceptor.validateSignAndToken = validateSignAndToken;
	}

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		// 防止因异常情况导致获取到之前线程ID重复的线程请求参数
		ApiParamCache.set(null);
		//允许跨域请求
		response.setHeader("Access-Control-Allow-Origin", "*");
		response.setHeader("Access-Control-Max-Age","3628800");
		response.setHeader("Access-Control-Allow-Methods","POST, GET, OPTIONS");
		response.setHeader("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With");
		// 验证当前请求内是否包含“param”参数
		String uri = ServletUtils.getUri(request);
		if (checkIgnorePrehandle(uri)){
			LOG.info("验证过滤请求通过.");
			return true;
		}
		String jsonParam = readJsonParameterFromRequest(request);
		LOG.info("拦截到请求 url:" + uri + ",参数" + jsonParam + ",client=" + HttpUtils.getClientIpAddr(request));
		if (!StringUtils.isEmpty(jsonParam) && handler != null && HandlerMethod.class.isAssignableFrom(handler.getClass())) {
			HandlerMethod handlerMethod = (HandlerMethod) handler;
			ApiParamRawType apiRawType = handlerMethod.getMethodAnnotation(ApiParamRawType.class);
			try {
				// 解析ApiParam
				ApiParam<?> apiParamObj = JsonUtilAlibaba.getApiParam(jsonParam, apiRawType.value());
				// 将转换后的ApiParam对象临时存储在线程缓存内
				ApiParamCache.set(apiParamObj);
				request.setAttribute(Constants.Common.WEB_REQUEST_PARAM, apiParamObj);
				if (validateSignAndToken) {
					String apiAuthKey = getApiAuthKeyByVersion(apiParamObj.getHead().getProtocolVersion());
					if(StringUtils.isBlank(apiAuthKey)){
						LOG.error("拦截到非法的API请求[protocolVersion校验失败] - " + apiParamObj.getHead().getProtocolVersion());
						writeErrorResponse(response, apiParamObj.getHead().getSequenceCode(),"xxxx",Common.BASE000008);
						return false;
					}
					// 验证ApiParam中的SIGN是否正确，如不正确则不提供任何服务
					if (!JsonUtilAlibaba.validateSign(jsonParam.trim(), apiParamObj.getSign(), apiAuthKey)) {
						LOG.error("拦截到非法的API请求[sign校验失败] - " + jsonParam);
						writeErrorResponse(response,apiParamObj.getHead().getSequenceCode(), apiAuthKey,Common.BASE000002);
						return false;
					}

					// ///////////////////////////////////////////////////////////////////////////
					// 验证ApiParam中的token是否正确，如不正确则不提供任何服务
					// ///////////////////////////////////////////////////////////////////////////
					if ( checkNeedLogin(uri) && !validateToken(apiParamObj.getHead().getToken(), apiParamObj.getHead().getUserId())) {
						LOG.error("拦截到非法的API请求[token校验失败] - " + jsonParam);
						writeTokenErrorResponse(response, apiParamObj.getHead().getSequenceCode(),apiAuthKey,Common.BASE0000100);
						return false;
					} else {
						//saveToken2Cache(apiParamObj.getHead().getToken(), apiParamObj.getHead().getUserId());
					}
					// API请求队列，如不能入队，则该次请求失效
					if (!queue.offer(apiParamObj.getHead().getSequenceCode())) {
						LOG.error("API请求队列已满，当前队列排队请求数：" + queue.size());
						writeErrorResponse(response, apiParamObj.getHead().getSequenceCode(),apiAuthKey,Common.BASE0000500);
						return false;
					}
				}
			} catch (Exception ex) {
				LOG.error("签名验证异常,msg:" + ex.getLocalizedMessage(), ex);
				return false;
			}
			return true;
		} else {
			LOG.warn("拦截到非法的API请求[获取不到param的参数]");
			writeErrorResponse(response, "00000000000001","xxxx",Common.BASE000001);
		}
		return false;
	}

	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

	}

	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
		LOG.info("进入到请求完成方法afterCompletion.");
		queue.poll();
		if (handler != null && HandlerMethod.class.isAssignableFrom(handler.getClass())) {
			HandlerMethod handlerMethod = (HandlerMethod) handler;
			ApiParam<?> apiParamObj = ApiParamCache.get(); 
			if (apiParamObj != null) { 
				String token = apiParamObj.getHead().getToken(); 
				String lastToken =  redisCacheHelper.getCache(CacheKey.Token + token,String.class); 
				if (StringUtils.isNotBlank(token)&&StringUtils.isNotBlank(lastToken)) {
					redisCacheHelper.expire(CacheKey.Token + token, cacheTime);
				}
				request.removeAttribute(Constants.WEB_SESSION);
			}
		}
	}

	/**
	 * 验证token和userId是否一致 
	 * @param tokenId	登录token
	 * @param userId	用户id
	 * @return 
	 * @return 返回boolean
	 */
	private boolean validateToken(String tokenId, String userId) {
		boolean isPass = false;
		try {
			if (StringUtils.isNotBlank(tokenId) && StringUtils.isNotBlank(userId)) {
				String value = redisCacheHelper.getCache(CacheKey.Token + tokenId,String.class);
				if (StringUtils.isNotBlank(value) && StringUtils.equals(value, userId)) {
					isPass = true;
				}
			}
		} catch (Exception e) {
			LOG.error("验证token异常,msg:"+e.getLocalizedMessage());
		}
		return isPass;
	}

	/**
	 * 保存token到缓存中 ,已在登录方法中写入cache
	 * @param tokenId	登录token
	 * @param userId 	用户id
	 * @return 返回void
	 */
	private void saveToken2Cache(String tokenId, String userId) {
		try {
			if (StringUtils.isNotBlank(tokenId) && StringUtils.isNotBlank(userId)) {
				redisCacheHelper.addCache(CacheKey.Token + tokenId, userId, cacheTime);
			}
		} catch (Exception e) {
			LOG.error("共享缓存补偿方法异常", e);
		}
	}

	/**
	 * 检查请求是否需要验证token
	 * @param uri 待验证的请求uri
	 * @return 返回boolean
	 */
	private boolean checkNeedLogin(String uri) {
		for (String ignoreUrl : IGNORE_TOKEN_URL) {
			if (uri.contains(ignoreUrl))
				return false;
		}
		return true;
	}
	
	/**
	 * 验证是否需要忽略预处理 
	 * @param uri
	 * @return 
	 * @return 返回boolean
	 */
	private boolean checkIgnorePrehandle(String uri) {
		for (String ignoreUrl : IGNORE_CHECK_URL) {
			if (uri.contains(ignoreUrl))
				return true;
		}
		return false;
	}


	/**
	 * 从请求中读取json串，支持post模式 
	 * @param request	请求
	 * @return 返回String
	 */
	private String readJsonParameterFromRequest(HttpServletRequest request){
		String jsonParam = request.getParameter("p");
		if(StringUtils.isEmpty(jsonParam)){
			try {
				StringBuffer buffer = new StringBuffer();
			    String line = null;
		        BufferedReader reader = request.getReader();
		        while((line = reader.readLine()) != null) {
		            buffer.append(line);
		        }
		        jsonParam=buffer.toString();
		        if(jsonParam.startsWith("p=")){
		        	jsonParam=jsonParam.substring(2);
		        }
		    } catch (Exception e) {
		    	LOG.error("读取post数据异常,",e);
		    }
		}
		return jsonParam;
	}

	/**
	 * 向response写通用的错误 
	 * @param response 
	 * @param sequenceCode	
	 * @param apiAuthKey
	 * @param errMsg 
	 * @return 返回void
	 */
	private void writeErrorResponse(HttpServletResponse response,String sequenceCode,String apiAuthKey,String errMsg){
		ApiResultHead head = ApiResultHead.getERROR(errMsg);
		head.setSequenceCode(sequenceCode);
		ApiResult<Object> apiResult = new ApiResult<Object>(head);
		apiResult.setBody(new Object());
		BaseController.writeJsonResponse(response,apiResult,apiAuthKey);
	}
	/**
	 * 向response写token失效错误 
	 * @param response http响应
	 * @param sequenceCode	序列号
	 * @param apiAuthKey 授权码
	 * @param errMsg 错误消息
	 * @return 返回void
	 */
	private void writeTokenErrorResponse(HttpServletResponse response,String sequenceCode,String apiAuthKey,String errMsg){
		ApiResultHead head = ApiResultHead.getTOKENERROR(errMsg);
		head.setSequenceCode(sequenceCode);
		ApiResult<Object> apiResult = new ApiResult<Object>(head);
		apiResult.setBody(new Object());
		BaseController.writeJsonResponse(response,apiResult,apiAuthKey);
	}

	/**
	 * 根据版本号获取授权key 
	 * @param apiParamObj
	 * @return 
	 * @return 返回String
	 */
	public static String getApiAuthKeyByVersion(String protocolVersion){
		return apiAuthKeyMap.get(protocolVersion);
	}
}
